---
layout: layouts/page.njk
title: Select
description: Displays a list of options for the user to pick from—triggered by a button.
toc:
  - label: Usage
    id: usage
    children:
      - label: HTML
        id: usage-html
      - label: HTML + JavaScript
        id: usage-html-js
        children:
          - label: "Step 1: Include the JavaScript files"
            id: usage-html-js-1
          - label: "Step 2: Add your select HTML"
            id: usage-html-js-2
          - label: HTML structure
            id: usage-html-js-3
          - label: JavaScript events
            id: usage-html-js-4
          - label: JavaScript methods
            id: usage-html-js-5
      - label: Jinja and Nunjucks
        id: usage-macro
  - label: Examples
    id: examples
    children:
      - label: Scrollable
        id: example-scrollable
      - label: Disabled
        id: example-disabled
      - label: With icon
        id: example-with-icon
---

{% from "macros/code_preview.njk" import code_preview %}
{% from "macros/code_block.njk" import code_block %}
{% from "select.njk" import select %}

{% set code_native %}<select class="select w-[180px]">
  <optgroup label="Fruits">
    <option>Apple</option>
    <option>Banana</option>
    <option>Blueberry</option>
    <option>Grapes</option>
    <option>Pineapple</option>
  </optgroup>
</select>{% endset %}
{{ code_preview("select-native", code_native) }}

{% set code_html %}
{{ select(
  trigger_attrs={"class": "w-[180px]"},
  is_combobox=true,
  items=[
    {
      type: "group",
      label: "Fruits",
      items: [
        { type: "item", value: "apple", label: "Apple", attrs: {"data-force": "true"} },
        { type: "item", value: "banana", label: "Banana", attrs: {"data-keywords": "bread"} },
        { type: "item", value: "blueberry", label: "Blueberry" },
        { type: "item", value: "pineapple", label: "Grapes" },
        { type: "item", value: "pineapple", label: "Pineapple" }
      ]
    }
  ]
) }}
{% endset %}

{% set code_script %}
{% endset %}

{{ code_preview("select", code_script | prettyHtml ~ code_html | prettyHtml) }}

<h2 id="usage"><a href="#usage">Usage</a></h2>

<h3 id="usage-html"><a href="#usage-html">HTML</a></h3>

<section class="prose">
  <p>If you use a <code>&lt;select&gt;</code> element, just add the <code>select</code> class to it or have a parent with the <code>form</code> class (<a href="/components/form">read more about form</a>).</p>
</section>

{{ code_block(code_native) }}

<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>

<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript files</a></h4>

<section class="prose">
  <p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code>&lt;head&gt;</code> of your page:</p>
</section>

{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/select.min.js" defer></script>{% endset %}
{{ code_block(code_script | prettyHtml, "html") }}

<div class="flex flex-wrap gap-2 my-6">
  <a class="badge-outline" href="/installation/#install-js">
    Components with JavaScript
    {% lucide "arrow-right" %}
  </a>
  <a class="badge-outline" href="/installation/#install-cli">
    Use the CLI
    {% lucide "arrow-right" %}
  </a>
  <a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/select.js" target="_blank">
    select.js
    {% lucide "arrow-right" %}
  </a>
</div>

<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your select HTML</a></h4>

{{ code_block(code_html | prettyHtml, "html") }}

<h4 id="usage-html-js-3"><a href="#usage-html-js-3">HTML structure</a></h4>

<section class="prose">
  <dl>
    <dt><code class="highlight language-html">&lt;div class="select"&gt;</code></dt>
    <dd>Wraps around the entire component.
      <dl>
        <dt><code class="highlight language-html">&lt;button type="button" popovertarget="{ POPOVER_ID }"&gt;</code></dt>
        <dd>
          <p>The trigger to open the popover, can also have the following attributes:</p>
          <ul>
            <li><code>id="{BUTTON_ID}"</code>: linked to by the <code>aria-labelledby</code> attribute of the listbox.</li>
            <li><code>aria-haspopup="listbox"</code>: indicates that the button opens a listbox.</li>
            <li><code>aria-controls="{ LISTBOX_ID }"</code>: points to the listbox's id.</li>
            <li><code>aria-expanded="false"</code>: tracks the popover's state.</li>
            <li><code>aria-activedescendant="{ OPTION_ID }"</code>: points to the active option's id.</li>
          </ul>
        </dd>
        <dt><code class="highlight language-html">&lt;div popover class="popover" id="{ POPOVER_ID }"&gt;</code></dt>
        <dd>As with the <a href="/components/popover">Popover</a> component, you can set up the side and alignment of the popover using the <code>data-side</code> and <code>data-align</code> attributes.
        <dl>
          <dt><code class="highlight language-html">&lt;div role="listbox"&gt;</code></dt>
          <dd>The listbox containing the options. Should have the following attributes:
            <ul>
              <li><code>id="{ LISTBOX_ID }"</code>: refered to by the <code>aria-controls</code> attribute of the trigger.</li>
              <li><code>aria-labelledby="{ BUTTON_ID }"</code>: linked to by the button's <code>id</code> attribute.</li>
            </ul>
            <dl>
              <dt><code class="highlight language-html">&lt;div role="option" data-value="{ VALUE }"&gt;</code></dt>
              <dd>Option that can be selected.Should have a unique id if you use the <code>aria-activedescendant</code> attribute on the trigger.</dd>
              <dt><code class="highlight language-html">&lt;hr role="separator"&gt;</code> <span class="badge-secondary">Optional</span></dt>
              <dd>Separator between groups/options.</dd>
              <dt><code class="highlight language-html">&lt;div role="group"&gt;</code> <span class="badge-secondary">Optional</span></dt>
              <dd>Group of options, can have a <code>aria-labelledby</code> attribute to link to a heading.</dd>
              <dt><code class="highlight language-html">&lt;span role="heading"&gt;</code></dt>
              <dd>Group heading, must have an <code>id</code> attribute if you use the <code>aria-labelledby</code> attribute on the group.</dd>
            </dl>
          </dd>
        </dl>
      </dl>
    </dd>
    <dt><code class="highlight language-html">&lt;input type="hidden" name="{ NAME }" value="{ VALUE }"&gt;</code> <span class="badge-secondary">Optional</span></dt>
    <dd>The hidden input that holds the value of the field (if needed).</dd>
  </dl>
</section>

<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript events</a></h4>

<section class="prose">
  <dl>
    <dt><code>basecoat:initialized</code></dt>
    <dd>Once the component is fully initialized, it dispatches a custom (non-bubbling) <code>basecoat:initialized</code> event on itself.</dd>
    <dt><code>basecoat:popover</code></dt>
    <dd>When the popover opens, the component dispatches a custom (non-bubbling) <code>basecoat:popover</code> event on <code>document</code>. Other popover components (Combobox, Dropdown Menu, Popover and Select) listen for this to close any open popovers.</dd>
    <dt><code>change</code></dt>
    <dd>When the selected value changes, the component dispatches a custom (bubbling) <code>change</code> event on itself, with the selected value in <code>event.detail</code> (e.g. <code>{ detail: { value: "something" }}</code>).</dd>
  </dl>
</section>

<h4 id="usage-html-js-5"><a href="#usage-html-js-5">JavaScript methods</a></h4>

<section class="prose">
  <dl>
    <dt><code>selectByValue</code></dt>
    <dd>
      <p>You can call this method on the component after it is initialized to select an option by value (i.e. the option with the matching <code>data-value</code> attribute):</p>

{% set code_script %}<script>
  const selectComponent = document.querySelector('#my-select');
  selectComponent.addEventListener('basecoat:initialized', () => {
    selectComponent.selectByValue('apple');
  });
</script>{% endset %}
{{ code_block(code_script | prettyHtml, "html") }}
    </dd>
  </dl>
</section>

<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>

<div class="prose">
  <p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">select()</code> Nunjucks or Jinja macro for this component.</p>
</div>

<div class="flex flex-wrap gap-2 my-6">
  <a class="badge-outline" href="/installation/#install-macros" target="_blank">
    Use Nunjucks or Jinja macros
    {% lucide "arrow-right" %}
  </a>
  <a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/dialog.html.jinja" target="_blank">
    Jinja macro
    {% lucide "arrow-right" %}
  </a>
  <a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/dialog.njk" target="_blank">
    Nunjucks macro
    {% lucide "arrow-right" %}
  </a>
</div>

{% set raw_code %}{% raw %}{{ select(
  items=[
    {
      type: "group",
      label: "Fruits",
      items: [
        { type: "item", value: "apple", label: "Apple" },
        { type: "item", value: "banana", label: "Banana" },
        { type: "item", value: "blueberry", label: "Blueberry" },
        { type: "item", value: "grapes", label: "Grapes" },
        { type: "item", value: "pineapple", label: "Pineapple" }
      ]
    }
  ]
) }}{% endraw %}{% endset %}
{{ code_block(raw_code, "jinja") }}

<h2 id="examples"><a href="#examples">Examples</a></h2>

<h3 id="example-scrollable"><a href="#example-scrollable">Scrollable</a></h3>

{% set code %}
{% call select(
  listbox_attrs={"class": "scrollbar overflow-y-auto max-h-64"}
) %}
{% for i in range(0, 99) %}
<div role="option" {% if i > 0 %}data-value="item-{{ i }}"{% endif %}>Item {{ i }}</div>
{% endfor %}
{% endcall %}
{% endset %}

{{ code_preview("select-scrollable", code | prettyHtml) }}

<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>

{% set code %}
{% call select(
  trigger_attrs={"disabled": "disabled"}
) %}
<div role="option" data-value="disabled">Disabled</div>
{% endcall %}
{% endset %}

{{ code_preview("select-disabled", code | prettyHtml) }}

<h3 id="example-with-icon"><a href="#example-with-icon">With icon</a></h3>

{% set code %}
{% call select(
  id="select-with-icons",
  trigger_attrs={'class': 'w-[180px]'}
) %}
<div type="button" role="option" data-value="bar">
  <span class="flex items-center gap-2">
    {% lucide "chart-bar", {"class": "text-muted-foreground"} %}
    Bar
  </span>
</div>
<div type="button" role="option" data-value="line">
  <span class="flex items-center gap-2">
    {% lucide "chart-line", {"class": "text-muted-foreground"} %}
    Line
  </span>
</div>
<div type="button" role="option" data-value="pie">
  <span class="flex items-center gap-2">
    {% lucide "chart-pie", {"class": "text-muted-foreground"} %}
    Pie
  </span>
</div>
{% endcall %}
{% endset %}

{{ code_preview("select-scrollable", code | prettyHtml) }}